using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using DS.SludgeEngine.Base;
using DS.SludgeEngine.Base.GlobalContent;

namespace DS.SludgeEngine.Terrain
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class Ground : Microsoft.Xna.Framework.DrawableGameComponent, ITerrainComponent
    {
        private int width, height;
        float[,] heightData;
        VertexPositionNormalTexture[] terrainVertices;

        VertexBuffer terrainVertexBuffer;
        IndexBuffer terrainIndexBuffer;
        VertexDeclaration terrainVertexDeclaration;

        //Textures for Terrain
        Texture2D grassTexture;

        //Effects for drawing terrain
        BasicEffect effect;

        //Matrices
        Matrix worldMatrix;
        Matrix reflectionViewMatrix;

        //Model
        Model terrainModel;

        public Ground(Game game) : base(game)
        {
            effect = new BasicEffect(GameInformation.Game.GraphicsDevice, null);
            effect.Alpha = 1.0f;
            effect.AmbientLightColor = new Vector3(0.25f, 0.25f, 0.25f);
            effect.TextureEnabled = true;

            effect.DirectionalLight0.DiffuseColor = Color.White.ToVector3();
            effect.SpecularColor = Color.Red.ToVector3();
            effect.DirectionalLight0.Direction = new Vector3(-1, -1, -1);
            effect.DirectionalLight0.Enabled = true;

            effect.LightingEnabled = true;

            worldMatrix = Matrix.Identity;
            reflectionViewMatrix = Matrix.Identity;

            this.terrainVertexDeclaration = new VertexDeclaration(GameInformation.Game.GraphicsDevice, VertexPositionNormalTexture.VertexElements);
        }

        #region DrawableGameComponent methods
        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here


            base.Initialize();
        }

        /// <summary>
        /// Load your graphics content.
        /// </summary>
        protected override void LoadContent()
        {

            
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            // TODO: Add your update code here

            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            ICameraComponent camera = (ICameraComponent)GameInformation.Game.Services.GetService(typeof(ICameraComponent));
            if (camera == null)
            {
                return;
            }
            //effect.World = worldMatrix;
            //effect.View = camera.ViewMatrix;
            //effect.Projection = camera.ProjectionMatrix;
            //effect.Texture = grassTexture;
            //effect.Begin();
            //foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            //{
            //    pass.Begin();

            //    //GameInformation.Game.GraphicsDevice.Vertices[0].SetSource(terrainVertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
            //    //GameInformation.Game.GraphicsDevice.Indices = terrainIndexBuffer;
            //    //GameInformation.Game.GraphicsDevice.VertexDeclaration = terrainVertexDeclaration;

            //    //int noVertices = terrainVertexBuffer.SizeInBytes / VertexPositionNormalTexture.SizeInBytes;
            //    //int noTriangles = terrainIndexBuffer.SizeInBytes / sizeof(int) / 3;
            //    //GameInformation.Game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, noVertices, 0, noTriangles);

            //    terrainModel.Meshes[0].Draw();

            //    pass.End();
            //}
            //effect.End();
            foreach (ModelMesh mesh in terrainModel.Meshes)
            {
                
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.World = worldMatrix;
                    effect.View = camera.ViewMatrix;
                    effect.Projection = camera.ProjectionMatrix;
                    effect.Texture = grassTexture;
                    effect.Alpha = 1.0f;
                    effect.AmbientLightColor = new Vector3(0.25f, 0.25f, 0.25f);
                    effect.TextureEnabled = true;

                    effect.DirectionalLight0.DiffuseColor = Color.White.ToVector3();
                    effect.SpecularColor = Color.Red.ToVector3();
                    effect.DirectionalLight0.Direction = new Vector3(-1, -1, -1);
                    effect.DirectionalLight0.Enabled = true;

                    effect.LightingEnabled = true;

                }
                mesh.Draw();
            }
        }
        #endregion

        #region Terrain Functions

        private void LoadHeightData(Texture2D heightMap)
        {

            float minimumHeight = float.MaxValue;
            float maximumHeight = float.MinValue;

            width = heightMap.Width;
            height = heightMap.Height;

            Color[] heightMapColors = new Color[width * height];
            heightMap.GetData(heightMapColors);

            heightData = new float[width, height];
            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                {
                    heightData[x, y] = heightMapColors[x + y * width].R;
                    if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y];
                    if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y];
                }

            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                {
                    heightData[x, y] = (heightData[x, y] / 255) * 30.0f;
                }
        }

        private void SetUpTerrainVertices()
        {
            terrainVertices = new VertexPositionNormalTexture[width * height];

            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    terrainVertices[x + y * width].Position = new Vector3(x, heightData[x, y], y);
                    terrainVertices[x + y * width].TextureCoordinate = new Vector2((float)x / (width - 1), (float)y / (height - 1));
                }
            }

            CalculateNormals(terrainVertices);

            terrainVertexBuffer = new VertexBuffer(GameInformation.Game.GraphicsDevice, VertexPositionNormalTexture.SizeInBytes * width * height, BufferUsage.None);
            terrainVertexBuffer.SetData(terrainVertices);
        }

        private void SetUpTerrainIndices()
        {
            int[] indices = new int[(width - 1) * (height - 1) * 6];
            int counter = 0;
            for (int y = 0; y < height - 1; y++)
            {
                for (int x = 0; x < width - 1; x++)
                {
                    int topLeft = x + y * width;
                    int topRight = (x + 1) + y * width;
                    int lowerLeft = x + (y + 1) * width;
                    int lowerRight = (x + 1) + (y + 1) * width;

                    indices[counter++] = topLeft;
                    indices[counter++] = lowerRight;
                    indices[counter++] = lowerLeft;

                    indices[counter++] = topLeft;
                    indices[counter++] = topRight;
                    indices[counter++] = lowerRight;
                }
            }

            terrainIndexBuffer = new IndexBuffer(GameInformation.Game.GraphicsDevice, typeof(int), (width - 1) * (height - 1) * 6, BufferUsage.None);
            terrainIndexBuffer.SetData(indices);
        }

        private void CalculateNormals(VertexPositionNormalTexture[] vertices)
        {
            //Obtain the index information from the index buffer we have
            //This will not have changed unless we changed heightmaps or performed some
            //sort of optimisation on the terrain mesh.
            int[] indices = new int[terrainIndexBuffer.SizeInBytes / sizeof(int)];
            terrainIndexBuffer.GetData<int>(indices);

            //calculate the normal for the last vertex at [width, height]
            int thisIndex = (width * height) - 1;
            int sideIndex = thisIndex - 1;
            int aboveIndex = thisIndex - width;

            //get Vectors to the side and below.
            Vector3 side = vertices[sideIndex].Position - vertices[thisIndex].Position;
            Vector3 above = vertices[aboveIndex].Position - vertices[thisIndex].Position;

            //cross the Vectors to get normal.
            vertices[thisIndex].Normal = Vector3.Cross(side, above);

            //work through vertices up to end and bottom boundary
            for (int y = 0; y < height - 1; y++)
            {
                for (int x = 0; x < width - 1; x++)
                {
                    thisIndex = x + y * width;
                    sideIndex = thisIndex + 1;
                    int belowIndex = x + (y + 1) * width;

                    //get vertices to the side and below.
                    side = vertices[sideIndex].Position - vertices[thisIndex].Position;
                    Vector3 below = vertices[belowIndex].Position - vertices[thisIndex].Position;

                    //cross the vertices to get normal.
                    vertices[thisIndex].Normal = Vector3.Cross(below, side);
                }
            }

            //Boundary cases: max height and  max width.
            int offset = ((height - 1) * width);
            for (int x = 0; x < width - 1; x++)
            {
                thisIndex = offset + x;
                sideIndex = thisIndex + 1;
                aboveIndex = thisIndex - width;

                //get vectors to the side and below.
                side = vertices[sideIndex].Position - vertices[thisIndex].Position;
                above = vertices[aboveIndex].Position - vertices[thisIndex].Position;

                //cross the Vectors to get normal.
                vertices[thisIndex].Normal = Vector3.Cross(side, above);
            }

            offset = width - 1;
            for (int y = 0; y < height - 1; y++)
            {
                thisIndex = offset + (y * width);
                sideIndex = thisIndex - 1;
                int belowIndex = thisIndex + width;

                //get Vectors to the side and below.
                side = vertices[sideIndex].Position - vertices[thisIndex].Position;
                Vector3 below = vertices[belowIndex].Position - vertices[thisIndex].Position;

                //cross the Vectors to get normal.
                vertices[thisIndex].Normal = Vector3.Cross(side, below);
                
            }
            terrainNormals = new Vector3[width, height];
            for (int y = 0; y < height - 1; y++)
            {
                for (int x = 0; x < width - 1; x++)
                {
                    vertices[x + y * width].Normal.Normalize();
                    terrainNormals[x, y] = vertices[x + y * width].Normal;
                }
            }
        }
        Vector3[,] terrainNormals;
        #endregion

        #region ITerrainComponent methods
        public Matrix ReflectionViewMatrix
        {
            get
            {
                return reflectionViewMatrix;
            }
        }
        public float MinHeight
        {
            get
            {
                return 0.0f;
            }
        }
        public Vector3 GetNormal(int x, int y)
        {
            if ((x >= 0 && x <= height - 2)
             && (y >= 0 && y <= width - 2))
            {

                return terrainNormals[x, y];
            }
            else
            {
                return new Vector3();
            }
        
        }

        public float GetTerrainHeight(float fTerX, float fTerY)
        {
            // we first get the height of 4 points of the quad underneath the point
            //float temp = fTerY;
            //fTerY = fTerX;
            //fTerX = temp;
            int x = (int)fTerX;
            int y = (int)fTerY;

            if ((x >= 0 && x <= height - 2)
                && (y >= 0 && y <= width - 2))
            {

                float fTriY0 = (heightData[x, y]);
                float fTriY1 = (heightData[x + 1, y]);
                float fTriY2 = (heightData[x, y + 1]);
                float fTriY3 = (heightData[x + 1, y + 1]);
                // find which of the 2 triangles the point is over (depends on how you render)
                // then take the height at a triangle point
                // and adjust by the slope down right-angle edges * distance along that edge.
                float fHeight;
                float fSqX = fTerX - x;
                float fSqY = fTerY - y;
                if ((fSqX + fSqY) < 1)
                {
                    fHeight = fTriY0;
                    fHeight += (fTriY1 - fTriY0) * fSqX;
                    fHeight += (fTriY2 - fTriY0) * fSqY;
                }
                else
                {
                    fHeight = fTriY3;
                    fHeight += (fTriY1 - fTriY3) * (1.0f - fSqY);
                    fHeight += (fTriY2 - fTriY3) * (1.0f - fSqX);
                }
                return fHeight;
            }
            return 0.0f;
        }

        public Vector3 PickTerrain(Ray ray, Vector3 myPosition)
        {
            Vector3 result = new Vector3();
            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                {
                    BoundingBox tmp = new BoundingBox(terrainVertices[x + y * width].Position, (terrainVertices[x + y * width].Position) + new Vector3(1f, 1f, 1f));

                    if (ray.Intersects(tmp) != null)
                    {
                        result = new Vector3(x, 0, -y);
                    }
                }
            return result;
        }

        public void LoadTerrain(Texture2D heightMap, Texture2D treeMap)
        {
        }
        
        public void LoadTerrain(Model terrain, Texture2D treeMap)
        {

            grassTexture = GlobalContent.Texture2Ds["Grass"];
            terrainModel = terrain;

            //LoadHeightData();
            //SetUpTerrainIndices();
            //SetUpTerrainVertices();
        }
        #endregion
    }
}